using DotNetCommon.Extensions;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace DotNetCommon.Expressions.Base;

internal class BaseVisit
{
    /// <summary>
    /// 生成当前节点的字符串描述
    /// </summary>
    /// <param name="node"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    protected virtual string GenerateFullMarkString(ExpressionNode node) => throw new NotImplementedException();
    /// <summary>
    /// 准备树形结构
    /// </summary>
    /// <param name="node"></param>
    /// <exception cref="NotImplementedException"></exception>
    protected virtual void Prepare(ExpressionNode node) => throw new NotImplementedException();
    /// <summary>
    /// 重新生成当前节点(当子节点有简化动作时,父节点要冒泡一样一级级跟着Rebuild)
    /// </summary>
    /// <param name="node"></param>
    /// <remarks>注意：简化后不能更改当前节点的类型,比如: [bool? ok;p=>ok??p.Id>1;] 整体是bool,Left是bool?, 简化后虽然使用了[ok]的值, 但整体类型应仍然是[bool]</remarks>
    /// <returns></returns>
    internal virtual Expression Rebuild(ExpressionNode node) => node.Expression;

    /// <summary>
    /// 简化表达式树入口
    /// </summary>
    /// <param name="node"></param>
    /// <param name="visit"></param>
    /// <param name="isKeepCallBack">是否保留此表达式,比如: 在 根据lambda解析生成update语句的时候,期望保留 new Person{}</param>
    /// <param name="isDisableReduce">是否禁止精简,用在 ExpressionHelper.GetFullMarkString 中</param>
    internal void Reduce(ExpressionNode node, Action<ExpressionNode> visit, Func<Expression, bool> isKeepCallBack, bool isDisableReduce = false)
    {
        Prepare(node);
        node.Children.ForEach(x => visit(x));
        //node.FullMarkString = GenerateFullMarkString(node);
        if (!isDisableReduce)
        {
            var isKeep = false;
            if (isKeepCallBack != null) isKeep = isKeepCallBack(node.Expression);
            SimpleReduce(node, isKeep);
            if (!isKeep) Reduce2(node);
        }
        //延迟生成当前节点的描述,因为在简化中用不到当前节点的
        node.FullMarkString = ExpressionHelper.GetVisit(node.NodeType.Value).GenerateFullMarkString(node);

    }
    /// <summary>
    /// 在SimpleReduce基础上再进行一次简化,处理短路的运算符,比如: lambda[p=>true||p.IsWrite] 会简化为 Constant[true]
    /// </summary>
    /// <param name="node"></param>
    protected virtual void Reduce2(ExpressionNode node) { }

    /// <summary>
    /// 通用节点简化方法,简化条件:
    /// <list type="bullet">
    /// <item>当前节点必须有两个或以上的子节点;</item>
    /// <item>子节点中必须同时存在含参数的节点和不含参数的节点;</item>
    /// </list>
    /// 这样, 不含参数的子节点将被简化, 如果所有子节点都不含参数, 那说明简化的时机还没到, 延迟到父节点进行判断
    /// </summary>
    /// <param name="node"></param>
    /// <param name="isKeep"></param>
    private void SimpleReduce(ExpressionNode node, bool isKeep)
    {
        //简化操作是在父节点根据子节点的情况判断是否时机成熟 从而对子节点进行简化
        //先扫描子节点的参数以及 Eval() 调用情况
        bool hasParameterFlag = false;//是否有子节点含有参数
        bool noParameterFlag = false;//是否有子节点不含参数
        bool hasEvalFlag = false;//是否有子节点含有Eval()标记
        var hasEvalFlags = new bool[node.Children.Count];//所有子节点的Eval()标记
        var parameters = node.GetParameters(EnumParameterTreeType.AllParentIncludeSelf);
        for (int i = 0; i < node.Children.Count; i++)
        {
            var child = node.Children[i];
            var flag = child.HasParameter = child.hasParameter(parameters);
            if (flag) hasParameterFlag = true;
            else noParameterFlag = true;
            if (child.Expression is MethodCallExpression call && call.Method.GetMethodGenericFullName().Name == "T DotNetCommon.Extensions.GenericExtensions.Eval<T>(T obj)")
                hasEvalFlag = hasEvalFlags[i] = true;
        }

        if ((hasParameterFlag && noParameterFlag)//子节点中同时具有含参数的和不含参数的 要进行简化
            || isKeep//用户指定保留当前节点 那么子节点也该简化了
            || node.NodeType == ExpressionType.Lambda && noParameterFlag//因为lambda自身无法被简化 所以子节点简化时机到了
            || hasEvalFlag//子节点中有强制简化的存在
            )
        {
            //进行简化
            for (int i = 0; i < node.Children.Count; i++)
            {
                var child = node.Children[i];
                if (
                    (!child.HasParameter || hasEvalFlags[i])
                    && child.Expression != null
                    && child.NodeType != ExpressionType.Lambda //lambda表达式和quote无法被简化, 见 ExpressionNode.cs 中的分析
                    && child.NodeType != ExpressionType.Quote
                    && child.NodeType != ExpressionType.Parameter
                    && child.NodeType != ExpressionType.Constant)
                {
                    //进行简化
                    var res = child.Reduce();
                    var para = Expression.Parameter(child.Expression.Type);
                    node.OutMidValues.Add(para, res);
                    node.Children[i] = new ExpressionNode
                    {
                        Parent = node,
                        Expression = para,
                        UpdateRequest = true,
                        FullMarkString = child.Expression.Type.GetClassFullName()
                    };
                }
            }
        }
        //如果子孙节点有更新 则 rebuid 当前节点
        if (node.NeedUpdate) node.Expression = Rebuild(node);
        if (isKeep)
        {
            //将自身标记为 parameter 防止被上级简化
            node.IsParameter = true;
            node.HasParameter = true;
            node.FakeParameter = Expression.Parameter(typeof(object));
            node.GetParameters(EnumParameterTreeType.RootParent).Add(node.FakeParameter);
        }
    }

    /// <summary>
    /// 获取表达式树访问的参数属性名称
    /// </summary>
    /// <param name="node"></param>
    /// <param name="visit"></param>
    /// <param name="parameters"></param>
    /// <param name="names"></param>
    internal virtual void GetAccessName(ExpressionNode node, Action<ExpressionNode> visit, List<ParameterExpression> parameters, List<List<string>> names)
    {
        Prepare(node);
        node.Children.ForEach(x => visit(x));
        if (node.NodeType == ExpressionType.MemberAccess)
        {
            var member = node.Expression as MemberExpression;
            if (member.Expression.NodeType == ExpressionType.Parameter)
            {
                var index = parameters.IndexOf(member.Expression as ParameterExpression);
                if (index >= 0)
                {
                    names[index].Add(member.Member.Name);
                }
            }
        }
    }
}
